package cc.flyflow.biz.process.service.impl;

import cc.flyflow.biz.api.ApiStrategyFactory;
import cc.flyflow.biz.flowable.service.IFlowService;
import cc.flyflow.biz.form.FormStrategyFactory;
import cc.flyflow.biz.process.entity.Process;
import cc.flyflow.biz.process.entity.ProcessInstanceRecord;
import cc.flyflow.biz.process.service.*;
import cc.flyflow.biz.service.IFileService;
import cc.flyflow.biz.utils.CoreHttpUtil;
import cc.flyflow.biz.vo.ProcessDataQueryVO;
import cc.flyflow.biz.vo.ProcessInstanceRecordVO;
import cc.flyflow.common.constants.ApproveResultEnum;
import cc.flyflow.common.constants.NodeUserTypeEnum;
import cc.flyflow.common.constants.ProcessInstanceConstant;
import cc.flyflow.common.constants.ProcessInstanceRecordStatusEnum;
import cc.flyflow.common.dto.PageResultDto;
import cc.flyflow.common.dto.R;
import cc.flyflow.common.dto.flow.FormItemVO;
import cc.flyflow.common.dto.flow.NodeUser;
import cc.flyflow.common.dto.process.ProcessInstanceDto;
import cc.flyflow.common.dto.process.ProcessInstanceParamDto;
import cc.flyflow.common.dto.process.ProcessQueryParamDto;
import cc.flyflow.common.dto.process.TaskDto;
import cc.flyflow.common.dto.third.UserDto;
import cc.flyflow.common.service.biz.IRemoteService;
import cc.flyflow.common.utils.JsonUtil;
import cc.flyflow.common.utils.TenantUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程实例 服务实现
 */
@Service
@Slf4j
public class ProcessInstanceServiceImpl implements IProcessInstanceService
{
    @Resource
    private IFileService fileService;
    @Resource
    private IFlowService flowService;

    @Resource
    private IProcessInstanceRecordService processInstanceRecordService;

    @Resource
    private IProcessInstanceCopyService processCopyService;

    @Resource
    private IProcessInstanceOperRecordService processInstanceOperRecordService;

    @Resource
    private IProcessNodeDataService processNodeDataService;

    @Resource
    private IProcessService processService;

    @Resource
    private IProcessInstanceNodeRecordService processNodeRecordService;

    @Resource
    private IProcessInstanceAssignUserRecordService processNodeRecordAssignUserService;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    @Lazy
    private IRemoteService remoteService;

    /**
     * 关闭流程实例
     *
     * @param processInstanceParamDto 流程实例对象
     * @return
     */
    public R stopProcessInstance(ProcessInstanceParamDto processInstanceParamDto)
    {
        /*
         * 通知逻辑从 ：
         *  先关闭通知，再状态更新通知=>
         *  先状态通知，再关闭通知（关闭通知在业务系统会被阻止）
         */
        //1.已撤销标识，用于通知业务系统
        ProcessInstanceParamDto dto = new ProcessInstanceParamDto();
        BeanUtil.copyProperties(processInstanceParamDto, dto);
        dto.setResult(3);
        dto.setCancel(true);
        JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(dto));
        sendBusiness(dto, jsonObject);

        //2.流程关闭处理
        R<String> r = CoreHttpUtil.endProcess(processInstanceParamDto);
        if (!r.isOk())
        {
            return R.fail(r.getMsg());
        }
        if (ObjUtil.isEmpty(processInstanceParamDto.getCancel()))
        {
            processInstanceParamDto.setCancel(true);
        }
        //3.流程状态变更-撤销
        editProcessStatus(processInstanceParamDto);
        return R.success();
    }

    /**
     * 启动流程
     *
     * @param processInstanceParamDto
     * @return
     */
    @Override
    public R startProcessInstance(ProcessInstanceParamDto processInstanceParamDto)
    {
        String uniqueId = processInstanceParamDto.getUniqueId();

        //业务key
        Process process = processService.getByUniqueId(uniqueId);
        if (process == null)
        {
            return R.fail("流程不存在");
        }
        if (process.getHidden() || process.getStop())
        {
            return R.fail("流程被禁用");
        }
        if (StrUtil.isNotBlank(processInstanceParamDto.getFlowId()))
        {
            if (!StrUtil.equals(process.getFlowId(), processInstanceParamDto.getFlowId()))
            {
                return R.fail("流程不存在");
            }
        }

        processInstanceParamDto.setFlowId(process.getFlowId());
        String userId = StpUtil.getLoginIdAsString();
        UserDto user = ApiStrategyFactory.getStrategy().getUser(userId);

        processInstanceParamDto.setStartUserId(String.valueOf(userId));
        Map<String, Object> paramMap = processInstanceParamDto.getParamMap();
        NodeUser rootUser =
                NodeUser.builder().id(userId).name(user.getName()).type(NodeUserTypeEnum.USER.getKey()).build();
        paramMap.put(ProcessInstanceConstant.VariableKey.STARTER_USER, CollUtil.newArrayList(rootUser));

        processInstanceParamDto.setBizKey(process.getUniqueId());

        //判断字段格式
        String formItems = process.getFormItems();
        List<FormItemVO> formItemVOS = JsonUtil.parseArray(formItems, FormItemVO.class);
        R checkValueResult = FormStrategyFactory.checkValue(formItemVOS, paramMap);
        if (!checkValueResult.isOk())
        {
            return R.fail(checkValueResult.getMsg());
        }

        R<String> r = CoreHttpUtil.startProcess(processInstanceParamDto);
        if (!r.isOk())
        {
            return R.fail(r.getMsg());
        }
        String data = r.getData();

        processInstanceOperRecordService.saveStartProcessRecord(userId, data, processInstanceParamDto.getFlowId());
        return R.success(data);
    }

    /**
     * 查询已办任务的流程实例
     *
     * @param pageVO
     * @return
     */
    @Override
    public R queryMineDoneProcessInstance(ProcessDataQueryVO pageVO)
    {
        List<String> flowIdList = pageVO.getFlowIdList();
        if (CollUtil.isNotEmpty(flowIdList))
        {
            flowIdList = processService.getAllRelatedFlowId(flowIdList).getData();
        }

        ProcessQueryParamDto processQueryParamDto = BeanUtil.copyProperties(pageVO, ProcessQueryParamDto.class);
        processQueryParamDto.setAssign(StpUtil.getLoginIdAsString());
        processQueryParamDto.setFlowIdList(flowIdList);
        R<PageResultDto<ProcessInstanceDto>> r = CoreHttpUtil.queryCompletedProcessInstance(processQueryParamDto);

        PageResultDto<ProcessInstanceDto> pageResultDto = r.getData();
        List<ProcessInstanceDto> records = pageResultDto.getRecords();
        if (CollUtil.isEmpty(records))
        {
            return R.success(pageResultDto);
        }

        Set<String> processInstanceIdSet =
                records.stream().map(w -> w.getProcessInstanceId()).collect(Collectors.toSet());

        //流程实例记录
        List<ProcessInstanceRecord> processInstanceRecordList =
                processInstanceRecordService.lambdaQuery().in(ProcessInstanceRecord::getProcessInstanceId,
                        processInstanceIdSet).list();

        //发起人
        Set<String> startUserIdSet =
                processInstanceRecordList.stream().map(w -> w.getUserId()).collect(Collectors.toSet());

        List<UserDto> startUserList = new ArrayList<>();
        {
            for (String userIds : startUserIdSet)
            {
                UserDto user = ApiStrategyFactory.getStrategy().getUser(userIds);
                startUserList.add(user);
            }
        }

        for (ProcessInstanceDto record : records)
        {
            ProcessInstanceRecord processInstanceRecord =
                    processInstanceRecordList.stream().filter(w -> StrUtil.equals(w.getProcessInstanceId(),
                            record.getProcessInstanceId())).findAny().orElse(null);

            if (processInstanceRecord != null)
            {
                record.setProcessName(processInstanceRecord.getName());

                UserDto startUser = startUserList.stream().filter(w -> w.getId()
                        .equals(processInstanceRecord.getUserId())).findAny().orElse(null);
                record.setGroupName(processInstanceRecord.getGroupName());
                record.setStartUserName(startUser.getName());
                record.setProcessInstanceResult(processInstanceRecord.getResult());
                record.setProcessInstanceStatus(processInstanceRecord.getStatus());
            }
        }

        return R.success(pageResultDto);
    }

    /**
     * 流程结束
     *
     * @param processInstanceParamDto
     * @return
     */
    @Override
    public R processEndEvent(ProcessInstanceParamDto processInstanceParamDto)
    {
        editProcessStatus(processInstanceParamDto);

        //已撤销标识，用于通知业务系统
        JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(processInstanceParamDto));
        if (processInstanceParamDto.getCancel())
        {
            jsonObject.put("result", "3");
        }

        //通知业务系统 流程已结束
        sendBusiness(processInstanceParamDto, jsonObject);
        log.info("结束流程参数>>>{}>>>{}", processInstanceParamDto, jsonObject);
        return R.success();
    }

    /**
     * 更新 工作流状态
     *
     * @param processInstanceParamDto
     */
    public void editProcessStatus(ProcessInstanceParamDto processInstanceParamDto)
    {
        /**
         * ACT_HI_TASKINST 任务记录
         * ACT_RU_TASK 实例任务
         * ACT_RU_IDENTITYLINK 流程实例
         * flyflow_process_instance_record flyflow组件流程记录
         */
        processInstanceRecordService.lambdaUpdate()
                .set(ProcessInstanceRecord::getEndTime, new Date())

                //结束操作
                .set(!processInstanceParamDto.getCancel(), ProcessInstanceRecord::getResult,
                        processInstanceParamDto.getResult())
                .set(!processInstanceParamDto.getCancel(), ProcessInstanceRecord::getStatus,
                        ProcessInstanceRecordStatusEnum.YJS.getCode())
                //撤销操作
                .set(processInstanceParamDto.getCancel(), ProcessInstanceRecord::getResult,
                        ApproveResultEnum.CANCEL.getValue())
                .set(processInstanceParamDto.getCancel(), ProcessInstanceRecord::getStatus,
                        ProcessInstanceRecordStatusEnum.YCX.getCode())

                .eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceParamDto.getProcessInstanceId())
                .eq(ProcessInstanceRecord::getTenantId, processInstanceParamDto.getTenantId())

                //因为 流程会先更新为结束状态，再执行撤销操作，所以，需要兼容 两种状态查询更新
                .and(e -> e
                        .eq(ProcessInstanceRecord::getStatus, ProcessInstanceRecordStatusEnum.JXZ.getCode())
                        .or()
                        .eq(ProcessInstanceRecord::getStatus, ProcessInstanceRecordStatusEnum.YJS.getCode())
                )

                .update(new ProcessInstanceRecord());
    }

    /**
     * 发送 业务通知
     *
     * @param processInstanceParamDto 流程参数
     * @param jsonObject              表单参数
     */
    public void sendBusiness(ProcessInstanceParamDto processInstanceParamDto, JSONObject jsonObject)
    {
        Thread thread = new Thread(() ->
        {
            // 异步任务
            log.info("异步任务正在运行...");
            try
            {
                //通知业务系统 流程已结束
                if (checkIsPass(processInstanceParamDto))
                {
                    Boolean b = ApiStrategyFactory.getStrategy().noticeBussiness(jsonObject);
                    log.info("[{}]:{}>>>{}", "流程结束(processEndEvent)", "通知业务系统", b);
                } // 模拟耗时操作
            } catch (Exception e)
            {
                log.error("异步发送业务通知::[{}]", e.getMessage(), e);
            }
            log.info("异步任务完成...");
        });

        thread.start();
    }

    /**
     * 检查 是不是通过 通知
     *
     * @param processInstanceParamDto 流程信息
     * @return
     */
    public boolean checkIsPass(ProcessInstanceParamDto processInstanceParamDto)
    {
        Map<String, Object> map = processInstanceParamDto.getParamMap();

        // 流程撤销时，会有一个结束通知，
        // cancel:false    result:1    paramMap.descType:pass
        if (!processInstanceParamDto.getCancel()
            && processInstanceParamDto.getResult() == 1
            && !"pass".equals(map.get("descType") + "")
        )
        {
            return false;
        }

        return true;
    }

    /**
     * 查询流程实例
     *
     * @param pageDto
     * @return
     */
    @Override
    public R queryList(ProcessDataQueryVO pageDto)
    {
        //查询所有的流程id
        List<String> allFlowIdList = new ArrayList<>();
        if (CollUtil.isNotEmpty(pageDto.getFlowIdList()))
        {
            List<String> data = processService.getAllRelatedFlowId(pageDto.getFlowIdList()).getData();
            allFlowIdList.addAll(data);
        }

        List<NodeUser> starterList = pageDto.getStarterList();
        List<String> startTime = pageDto.getStartTime();
        List<String> finishTime = pageDto.getFinishTime();
        Page<ProcessInstanceRecord> instanceRecordPage = processInstanceRecordService.lambdaQuery()
                .eq(ProcessInstanceRecord::getTenantId, TenantUtil.get())
                .in(CollUtil.isNotEmpty(allFlowIdList), ProcessInstanceRecord::getFlowId,
                        allFlowIdList)
                .eq(pageDto.getStatus() != null, ProcessInstanceRecord::getStatus, pageDto.getStatus())
                .ge(CollUtil.isNotEmpty(startTime) && startTime.size() >= 2, ProcessInstanceRecord::getCreateTime,
                        (CollUtil.isNotEmpty(startTime) && startTime.size() >= 2) ?
                                (DateUtil.parseDate(startTime.get(0))) : null
                )
                .le(CollUtil.isNotEmpty(startTime) && startTime.size() >= 2, ProcessInstanceRecord::getCreateTime,
                        (CollUtil.isNotEmpty(startTime) && startTime.size() >= 2) ?
                                (DateUtil.endOfDay(DateUtil.parseDate(startTime.get(1)))) : null
                )
                .ge(CollUtil.isNotEmpty(finishTime) && finishTime.size() >= 2, ProcessInstanceRecord::getEndTime,
                        (CollUtil.isNotEmpty(finishTime) && finishTime.size() >= 2) ?
                                (DateUtil.parseDate(finishTime.get(0))) : null
                )
                .le(CollUtil.isNotEmpty(finishTime) && finishTime.size() >= 2, ProcessInstanceRecord::getEndTime,
                        (CollUtil.isNotEmpty(finishTime) && finishTime.size() >= 2) ?
                                (DateUtil.endOfDay(DateUtil.parseDate(finishTime.get(1)))) : null
                )
                .in(CollUtil.isNotEmpty(starterList), ProcessInstanceRecord::getUserId, starterList == null ?
                        new ArrayList<>() : starterList.stream().map(w -> w.getId()).collect(Collectors.toList()))
                .eq(StrUtil.isNotBlank(pageDto.getProcessBizCode()), ProcessInstanceRecord::getProcessInstanceBizCode
                        , pageDto.getProcessBizCode())
                .orderByDesc(ProcessInstanceRecord::getCreateTime)
                .page(new Page<>(pageDto.getPageNum(), pageDto.getPageSize()));

        List<ProcessInstanceRecord> records = instanceRecordPage.getRecords();
        if (CollUtil.isEmpty(records))
        {
            return R.success(instanceRecordPage);
        }

        //流程配置
        Set<String> flowIdSet = records.stream().map(w -> w.getFlowId()).collect(Collectors.toSet());
        List<Process> processList = processService.lambdaQuery()
                .in(Process::getFlowId, flowIdSet)
                .eq(Process::getTenantId, TenantUtil.get())
                .list();

        List<ProcessInstanceRecordVO> processInstanceRecordVOList = BeanUtil.copyToList(records,
                ProcessInstanceRecordVO.class);

        for (ProcessInstanceRecordVO record : processInstanceRecordVOList)
        {
            UserDto userDto = ApiStrategyFactory.getStrategy().getUser(record.getUserId());

            record.setFormData(null);
            record.setProcess(null);
            record.setRootUserAvatarUrl(userDto.getAvatarUrl());
            record.setRootUserName(userDto.getName());
        }
        Page page = BeanUtil.copyProperties(instanceRecordPage, Page.class);
        page.setRecords(processInstanceRecordVOList);

        return R.success(page);
    }

    /**
     * 查询流程实例详情
     *
     * @param processInstanceId
     * @return
     */
    @Override
    public R queryDetailByProcessInstanceId(String processInstanceId)
    {
        ProcessInstanceRecord processInstanceRecord = processInstanceRecordService.lambdaQuery()
                .eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceId)
                .eq(ProcessInstanceRecord::getTenantId, TenantUtil.get())
                .one();

        ProcessInstanceRecordVO record = BeanUtil.copyProperties(processInstanceRecord, ProcessInstanceRecordVO.class);

        //流程配置
        UserDto userDto = ApiStrategyFactory.getStrategy().getUser(processInstanceRecord.getUserId());

        record.setFormData(null);
        record.setProcess(null);
        record.setRootUserAvatarUrl(userDto.getAvatarUrl());
        record.setRootUserName(userDto.getName());
        record.setCancelEnable(false);

        return R.success(record);
    }

    /**
     * 查询处理中的任务
     *
     * @param processInstanceId
     * @return
     */
    @Override
    public R queryTaskListInProgress(String processInstanceId)
    {
        R<List<TaskDto>> listR = CoreHttpUtil.queryTaskAssignee(null, processInstanceId);
        if (!listR.isOk())
        {
            return listR;
        }
        List<TaskDto> taskDtoList = listR.getData();
        for (TaskDto taskDto : taskDtoList)
        {
            UserDto userDto = ApiStrategyFactory.getStrategy().getUser(taskDto.getAssign());
            taskDto.setUserName(userDto.getName());
        }

        return listR;
    }

}
